package edu.upenn.seas.peerreview.smui

/**
 * This file defines all common object interfaces between Battleship application
 * code. The state machine, user interface and logical components should have
 * common knowledge about the described objects in this file.
 */

/**
 * An (x, y) tuple.
 */
 case class Point(val x : Int, val y : Int){
   def <(that: Point) = (x,y)<(that.x, that.y)
 }

/**
 * Factory: creates a new, blank board having the given width and height.
 */
object Board {
  def makeBoard(w : Int, h : Int) : Board = new BoardImpl(w, h)
  def makeBoard(p : Point) : Board = makeBoard(p.x, p.y)
}

/**
 * A board should have width > 0, height > 0, and is 0-indexed in both
 * dimensions.
 */
trait Board {
  def width : Int
  def height : Int
  def examine(coord : Point) : CellStatus
  def +(coord : Point, change : CellStatus) : Board
  
  // Implemented methods.
  final def +(i : Int, j : Int, change : CellStatus) : Board = this.+(Point(i,j), change)
  final def examine(i : Int, j : Int) : CellStatus = examine(Point(i, j))
  final def inRange(coord : Point) = coord.x >= 0 && coord.y >= 0 && 
      coord.x < width && coord.y < height
  final def size() : Point = Point(width, height)
}

/**
 * Enum: the status of a point in the ocean.
 */
sealed abstract class CellStatus;
object CellStatus {
  case object Hit extends CellStatus    // FiredAt, Ship
  case object Miss extends CellStatus   // FiredAt, No Ship
  case class Ship(ship : Vessel) extends CellStatus   // NotFiredAt, Ship
  case object Empty extends CellStatus  // NotFiredAt, Unknown Ship Status
  case object Invalid extends CellStatus // Bad position
}

/**
 * A vessel has a size and knows its type (tugboard, destroyer, etc).
 */
case class Vessel(val size : Int) {
  def name = size match {
    case 1 => "Floating Cardboard Box"
    case 2 => "Bamboo Raft"
    case 3 => "Tugboat"
    case 4 => "Submarine"
    case _ => "Battleship"
  }
  def source = Images.get(size match {
    case 1 => "1_cardboard_box.gif"
    case 2 => "2_raft.gif"
    case 3 => "3_tugboat.gif"
    case 4 => "4_submarine.gif"
    case _ => "5_battleship.gif"
  })
}

/**
 * Has knowledge of where the image resources are located.
 */
object Images {
  val directory = "./images/"
  def get(file: String) = directory + file
}

/**
 * Knows the "game logic" and performs game-specific action on behalf of the
 * state machine.
 * 
 * <p>This is Battleship specific.
 */
trait BattleshipLogic {
  
  case class Ship(bow:Point, stern:Point, links:Map[Point, Boolean]) {
    def sunk() = links.values.forall(_ == false)    
    def shootAt(p:Point) = Ship(bow, stern, links.update(p, false)) // returns copy of this ship with that index gone
    def points() = links.keys.toList
    def this(bow:Point, stern:Point) = { //fully healthy ship
      this(bow, stern, {
        val bowT = (bow.x, bow.y)
        val sternT = (stern.x, stern.y)
        val topLeft = if (bowT < sternT) bow else stern
        val botRight = if (bowT < sternT) stern else bow
        
        if ((topLeft.x != botRight.x) && (topLeft.y != botRight.y))
          Map()
        else
          Map() ++ {for(x <- topLeft.x to botRight.x; 
                        y <- topLeft.y to botRight.y) 
                        yield((Point(x,y) -> true))}
      })
    }
  }

  /**
   * Called to determine whether the game loop is running and game messages 
   * should be handled.
   */
  def isGameLoopRunning: Boolean 

  /**
   * Called when configs are sent.
   */
  def onInit(c: NewGameConfigs, myId: Id, uiId: Id, myManagerId: Id)

  /**
   * Called when ui places a ship, returns true iff all ships have been placed.
   * 
   * @param bow the top or left of the ship
   * @param stern the bottom or right of the ship
   */
  def onPlaceShip(bow: Point, stern: Point): Boolean 

  /**
   * Called when an opponent attacks me.
   */
  def onFireAtMe(attacker: Id, targetPoint: Point) 

  /**
   * Called after all attacks have been made and it is time to send
   *   fireresults and playerresults.
   * Input:
   *        sendFireResult =
   *          function (recipient, (attacker, targetPoint, result)) => Unit
   *          which sends a fire result to the given recipient (a player or ui)
   *        sendPlayerResult =
   *          function (recipient, (numHits, numSunks, numKills, iAmWinner)) => Unit
   *          which sends a player result to the given recipient (a player or manager)
   *
   * Recipient is the person receiving this message. In correct logic, for any given
   *   fireresult or playerresult all recipients (all opponents, yourself*,
   *   and your ui or manager depending on message type) will get the same message. 
   *
   *  -- *calling this function with yourself as a recepient is necessary to update
   *   the SM state, even if no message is actually sent to transport layer
   */
  def onHandleAttacks(sendFireResult: (Id, (Id, Point, FireResultType)) => Unit,
      sendPlayerResult: (Id, (Int, Int, Int, Boolean)) => Unit)

  /**
   * Called when a fireresult has been received.
   */
  def onFireResult(attacker: Id, attackee: Id, targetPoint: Point, result: FireResultType)

  /**
   * Called when you are the last player standing.
   */
  def onLastAlive(sendPlayerResult: (Id, (Int, Int, Int, Boolean)) => Unit)

  /**
   * Called when an individual game is over.
   */
  def onGameOver(sendPRMessage: Message => Unit)
  
  /**
   * Called when tournament is over.
   */
  def onTournamentOver(sendPRMessage: Message => Unit)
}
